﻿using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.IO;
using System.IO.Packaging;
using OpenLS.Drawing;
using OpenLS.Spreadsheet.Drawing;
using OpenLS.Spreadsheet.Formats.Biff.Records;

namespace OpenLS.Spreadsheet.Formats.Biff.OfficeArt
{
    internal class OfficeArtSpContainer : OfficeArtSpgrContainerFileBlock
    {
        private OfficeArtChildAnchor _childAnchor;
        private OfficeArtClientAnchorSheet _clientAnchor;
        private OfficeArtClientData _clientData;
        private OfficeArtClientTextbox _clientTextbox;
        private OfficeArtFPSPL _deletedShape;
        private OfficeArtFSPGR _shapeGroup;
        private OfficeArtFOPT _shapePrimaryOptions;
        private OfficeArtFSP _shapeProp;
        private OfficeArtSecondaryFOPT _shapeSecondaryOptions1;
        private OfficeArtSecondaryFOPT _shapeSecondaryOptions2;
        private OfficeArtTertiaryFOPT _shapeTertiaryOptions1;
        private OfficeArtTertiaryFOPT _shapeTertiaryOptions2;

        public override RecType RecordType
        {
            get { return RecType.OfficeArtSpContainer; }
        }


        protected override byte Version
        {
            get { return 0xF; }
        }

        protected override int ByteLength
        {
            get
            {
                var subrecords = new OfficeArtRecord[]
                                     {
                                         _shapeGroup,
                                         _shapeProp,
                                         _deletedShape,
                                         _shapePrimaryOptions,
                                         _shapeSecondaryOptions1,
                                         _shapeTertiaryOptions1,
                                         _childAnchor,
                                         ClientAnchor,
                                         _clientData,
                                         ClientTextbox,
                                         _shapeSecondaryOptions2,
                                         _shapeTertiaryOptions2,
                                     };
                return Enumerable.Sum(subrecords, item => (item == null) ? 0 : item.FullByteLength);
            }
        }

        public OfficeArtClientTextbox ClientTextbox
        {
            get { return _clientTextbox; }
        }

        public OfficeArtClientAnchorSheet ClientAnchor
        {
            get { return _clientAnchor; }
        }

        public override uint SpId
        {
            get { return _shapeProp.SpId; }
        }


        public void AddProperty(OfficeArtFOPTE property)
        {
            if (_shapePrimaryOptions == null)
                _shapePrimaryOptions = new OfficeArtFOPT();
            _shapePrimaryOptions.AddProperty(property);
        }

        public static OfficeArtSpContainer CreateFromComment(Comment comment, uint spid, OfficeArtWriteContext context)
        {
            var result = new OfficeArtSpContainer();
            result._clientData = new OfficeArtClientData(comment, context);
            result._clientTextbox = new OfficeArtClientTextbox(comment, context);
            result._clientAnchor = OfficeArtClientAnchorSheet.FromAnchor(new AbsoluteAnchor(comment.Cell.Worksheet)
                                                                             {
                                                                                 ActualHeight = comment.Height,
                                                                                 ActualWidth = comment.Width,
                                                                                 ColumnOffset = comment.LeftMargin,
                                                                                 RowOffset = comment.TopMargin
                                                                             });
            result._shapeProp = new OfficeArtFSP(spid) {FHaveAnchor = true};
            foreach (OfficeArtFOPTE prop in GetCommentProperties())
                result.AddProperty(prop);
            result._shapeProp.ShapeType = MSOSPT.msosptTextBox;
#if DEBUG
            result._shapeProp.CheckCompatible(result.ClientAnchor, result.ClientTextbox, result._shapePrimaryOptions);
#endif
            return result;
        }

        public static IEnumerable<OfficeArtFOPTE> GetCommentProperties()
        {
            yield return new OfficeArtFOPTE(OpId.LTxid, 192621248);
            yield return new OfficeArtFOPTE(OpId.TxflTextFlow, MSOTXFL.msotxflHorzN);
            yield return new OfficeArtFOPTE(OpId.Txdir, MSOTXDIR.msotxdirContext);
            yield return new OfficeArtFOPTE(OpId.TextBooleanProperties,
                                            TextBooleanProperties.fAutoTextMargin |
                                            TextBooleanProperties.fUsefAutoTextMargin);
            yield return new OfficeArtFOPTE(OpId.Cxk, Msocxk.msocxkNone);
            yield return
                new OfficeArtFOPTE(OpId.FillColor,
                                   new OfficeArtCOLORREF(255, 255, 225, OfficeArtCOLORREF.ColorFlags.None));
            yield return new OfficeArtFOPTE(OpId.FillBackColor,
                                            new OfficeArtCOLORREF(255, 255, 225, OfficeArtCOLORREF.ColorFlags.None));
            yield return new OfficeArtFOPTE(OpId.FillCrMod, OfficeArtCOLORREF.SysColor(244));
            yield return new OfficeArtFOPTE(OpId.FillStyleBooleanProperties, (FillStyleBooleanProperties) 1048592);
            yield return new OfficeArtFOPTE(OpId.LineCrMod, OfficeArtCOLORREF.SysColor(244));

            yield return
                new OfficeArtFOPTE(OpId.ShadowColor, new OfficeArtCOLORREF(0, 0, 0, OfficeArtCOLORREF.ColorFlags.None));
            yield return new OfficeArtFOPTE(OpId.ShadowCrMod, OfficeArtCOLORREF.SysColor(244));
            yield return new OfficeArtFOPTE(OpId.ShadowStyleBooleanProperties,
                                            ShadowStyleBooleanProperties.fShadow |
                                            ShadowStyleBooleanProperties.fshadowObscured |
                                            ShadowStyleBooleanProperties.fUsefShadow |
                                            ShadowStyleBooleanProperties.fUsefshadowObscured);
            yield return new OfficeArtFOPTE(OpId.GroupShapeBooleanProperties,
                                            GroupShapeBooleanProperties.fUsefHidden);
        }

        public static OfficeArtSpContainer CreateGroupContainer(uint spid)
        {
            var result = new OfficeArtSpContainer();
            result._shapeProp = new OfficeArtFSP(spid) {FGroup = true, FPatriarch = true};
            result._shapeGroup = new OfficeArtFSPGR();
            return result;
        }

        protected override void ReadNoHeader(OfficeArtReadContext context)
        {
#if DEBUG
            var pos = context.BaseStream.Position;
#endif
            _shapeGroup = context.ReadOptionalRecord<OfficeArtFSPGR>(RecType.OfficeArtFSPGR);
            _shapeProp = context.ReadOptionalRecord<OfficeArtFSP>(RecType.OfficeArtFSP);
            _deletedShape = context.ReadOptionalRecord<OfficeArtFPSPL>(RecType.OfficeArtFPSPL);
            _shapePrimaryOptions = context.ReadOptionalRecord<OfficeArtFOPT>(RecType.OfficeArtFOPT);
            _shapeSecondaryOptions1 = context.ReadOptionalRecord<OfficeArtSecondaryFOPT>(RecType.OfficeArtSecondaryFOPT);
            _shapeTertiaryOptions1 = context.ReadOptionalRecord<OfficeArtTertiaryFOPT>(RecType.OfficeArtTertiaryFOPT);
            _childAnchor = context.ReadOptionalRecord<OfficeArtChildAnchor>(RecType.OfficeArtChildAnchor);
#if DEBUG
            Contract.Assert(context.BaseStream.Position - pos <= this.rhrecLen);
#endif
            _clientAnchor = context.ReadOptionalRecord<OfficeArtClientAnchorSheet>(RecType.OfficeArtClientAnchorSheet);
            _clientData = context.ReadOptionalRecord<OfficeArtClientData>(RecType.OfficeArtClientData);
            _clientTextbox = context.ReadOptionalRecord<OfficeArtClientTextbox>(RecType.OfficeArtClientTextbox);
            if (_shapeSecondaryOptions1 == null)
            {
                _shapeSecondaryOptions2 =
                    context.ReadOptionalRecord<OfficeArtSecondaryFOPT>(RecType.OfficeArtSecondaryFOPT);
            }
            if (_shapeTertiaryOptions1 == null)
            {
                _shapeTertiaryOptions2 = context.ReadOptionalRecord<OfficeArtTertiaryFOPT>(RecType.OfficeArtTertiaryFOPT);
            }
        }

        protected override void WriteNoHeader(OfficeArtWriteContext context)
        {
#if DEBUG
            long position = context.BaseStream.Position;
            Contract.Assert(_shapeProp != null);
            _shapeProp.CheckCompatible(_clientAnchor, _clientTextbox, _shapePrimaryOptions);
#endif

            context.WriteOptionalChildren(_shapeGroup,
                                          _shapeProp,
                                          _deletedShape,
                                          _shapePrimaryOptions,
                                          _shapeSecondaryOptions1,
                                          _shapeTertiaryOptions1,
                                          _childAnchor,
                                          _clientAnchor,
                                          _clientData,
                                          _clientTextbox,
                                          _shapeSecondaryOptions2,
                                          _shapeTertiaryOptions2
                );

#if DEBUG
            Contract.Assert(context.BaseStream.Position - position == rhrecLen);
#endif
        }

        public override Anchor GetClientAnchorSheet(BiffReaderWorksheetContext context)
        {
            if (ClientAnchor == null)
                return null;
            Anchor result = ClientAnchor.ToAnchor(context.Worksheet);
            if (_shapePrimaryOptions != null)
            {
                int? blipId = _shapePrimaryOptions.GetOptionalIntegerProperty(OpId.Pib);
                if (blipId != null)
                {
                    MsoDrawingGroupRecord groupRecord = context.WorkbookContext.DrawingGroup;
                    Contract.Assert(groupRecord != null);
                    Picture picture = groupRecord.ChildRecord.GetBlip((int) blipId).ToPicture();
                    string description = _shapePrimaryOptions.GetOptionalStringProperty(OpId.WzDescription);
                    string name = _shapePrimaryOptions.GetOptionalStringProperty(OpId.WzName);
                    string title = null;
                    if (_shapeTertiaryOptions1 != null)
                    {
                        var blob = (Package) _shapeTertiaryOptions1.GetOptionalProperty(OpId.MetroBlob);
                        if (blob != null)
                        {
                            MetroBlob metroBlob = new MetroBlob(blob);
if (metroBlob.AnchorWrapper != null)
{
    var picture2 = metroBlob.AnchorWrapper.Item.Content as Picture;
                                if (picture2 != null && picture2.PictureNonVisual != null &&
                                    picture2.PictureNonVisual.NonVisualDrawingProps != null)
                                {
                                    title = picture2.PictureNonVisual.NonVisualDrawingProps.Title;
                                }
                            }
                        }
                    }
                    if (description != null)
                        picture.PictureNonVisual.NonVisualDrawingProps.Description = description;
                    if (name != null)
                        picture.Name = name;
                    if (title != null)
                        picture.PictureNonVisual.NonVisualDrawingProps.Title = title;
                    result.Content = XamlElement.FromPicture(picture, null);
                }
                return result;
            }
            return null;
        }

        public static OfficeArtSpContainer CreateFromPictureAnchor(Anchor anchor, uint spid,
                                                                   OfficeArtWriteContext context)
        {
            var result = new OfficeArtSpContainer();
            result._shapeProp = new OfficeArtFSP(spid);
            result._shapeProp.FHaveAnchor = true;
            result._shapeProp.ShapeType = MSOSPT.msosptPictureFrame;
            Picture picture;
            picture = context.WorkbookContext.PictureContext.GetPicture(anchor.Content);
            result._clientAnchor = OfficeArtClientAnchorSheet.FromAnchor(anchor);
            foreach (
                OfficeArtFOPTE prop in
                    GetPictureProperties(context.GetBlipId(picture.BlipFillProperties.Blip), picture.Name,
                                         picture.PictureNonVisual.NonVisualDrawingProps.Description,
                                         picture.PictureNonVisual.NonVisualDrawingProps.Title, anchor))
                result.AddProperty(prop);
            return result;
        }

        private static IEnumerable<OfficeArtFOPTE> GetPictureProperties(int blipId, string name, string description,
                                                                        string title, Anchor anchor)
        {
            yield return
                new OfficeArtFOPTE(OpId.ProtectionBooleanProperties,
                                   ProtectionBooleanProperties.fLockAspectRatio |
                                   ProtectionBooleanProperties.fUsefLockAgainstGrouping |
                                   ProtectionBooleanProperties.fUsefLockAdjustHandles |
                                   ProtectionBooleanProperties.fUsefLockVertices |
                                   ProtectionBooleanProperties.fUsefLockCropping |
                                   ProtectionBooleanProperties.fUsefLockAgainstSelect |
                                   ProtectionBooleanProperties.fUsefLockPosition |
                                   ProtectionBooleanProperties.fUsefLockAspectRatio |
                                   ProtectionBooleanProperties.fUsefLockRotation);
            yield return new OfficeArtFOPTE(OpId.Pib, blipId);
            if (!string.IsNullOrEmpty(description))
                yield return new OfficeArtFOPTE(OpId.PibName, description);
            yield return
                new OfficeArtFOPTE(OpId.BlipBooleanProperties,
                                   BlipBooleanProperties.fUsefPictureBiLevel | BlipBooleanProperties.fUsefPictureGray);
            yield return new OfficeArtFOPTE(OpId.FillStyleBooleanProperties, FillStyleBooleanProperties.fUsefFilled);
            yield return
                new OfficeArtFOPTE(OpId.LineStyleBooleanProperties,
                                   LineStyleBooleanProperties.fArrowheadsOK | LineStyleBooleanProperties.fUsefLine |
                                   LineStyleBooleanProperties.fUsefArrowheadsOK);
            yield return
                new OfficeArtFOPTE(OpId.ShapeBooleanProperties,
                                   ShapeBooleanProperties.fPreferRelativeResize |
                                   ShapeBooleanProperties.fUsefLockShapeType |
                                   ShapeBooleanProperties.fUsefPreferRelativeResize);
            if (!string.IsNullOrEmpty(name))
                yield return new OfficeArtFOPTE(OpId.WzName, name);
            if (!string.IsNullOrEmpty(description))
                yield return new OfficeArtFOPTE(OpId.WzDescription, description);
            if (!string.IsNullOrEmpty(title))
            {
                MemoryStream stream = new MemoryStream();
                Package metroPackage = Package.Open(stream, FileMode.Create, FileAccess.ReadWrite);
                AnchorWrapper anchorWrapper = new AnchorWrapper(anchor);
                MetroBlob blob = new MetroBlob {AnchorWrapper = anchorWrapper};
                blob.WritePackage(metroPackage);
                metroPackage.Close();
                stream.Flush();
                byte [] bytes = new byte[stream.Length];
                stream.Position = 0;
                stream.Read(bytes, 0, bytes.Length);
                yield return new OfficeArtFOPTE(OpId.MetroBlob, bytes);
            }
            yield return new OfficeArtFOPTE(OpId.GroupShapeBooleanProperties, GroupShapeBooleanProperties.fUsefHidden);
        }
    }
}